﻿//CandleBuilderContainer.cs
//Copyright (c) 2013 StockSharp LLC, all rights reserved.
//This code module is part of StockSharp library.
//This code is licensed under the GNU GENERAL PUBLIC LICENSE Version 3.
//See the file License.txt for the license details.
//More info on: http://stocksharp.com

namespace StockSharp.Algo.Candles.Compression
{
	using System;
	using System.Collections.Generic;
	using System.Linq;

	using Ecng.Collections;
	using Ecng.Common;

	/// <summary>
	/// Стандартный контейнер данных.
	/// </summary>
	public class CandleBuilderContainer : ICandleBuilderContainer
	{
		private sealed class SeriesInfo
		{
			private readonly SynchronizedDictionary<Candle, SynchronizedLinkedList<ICandleBuilderSourceValue>> _candleValues = new SynchronizedDictionary<Candle, SynchronizedLinkedList<ICandleBuilderSourceValue>>(1000);
			private readonly CandleBuilderContainer _container;

			private DateTime _firstValueTime;
			private DateTime _lastValueTime;

			public SeriesInfo(CandleBuilderContainer container)
			{
				if (container == null)
					throw new ArgumentNullException("container");

				_container = container;
			}

			public void Reset(DateTime from)
			{
				_firstValueTime = from;

				_candleValues.Clear();
			}

			public void RecycleValues()
			{
				// pyh: ValueKeepTime=0 соотвествует варианту "не хранить"
				// вечное хранение это TimeSpan.MaxValue

				//if (_container.ValuesKeepTime == TimeSpan.Zero)	
				//	return;

				var diff = _lastValueTime - _firstValueTime;
				if (diff > _container._maxValuesKeepTime)
				{
					lock (_candleValues.SyncRoot)
					{
						_firstValueTime = _lastValueTime - _container.ValuesKeepTime;

						var deleteKeys = new List<Candle>();

						foreach (var pair in _candleValues)
						{
							if (pair.Key.OpenTime < _firstValueTime)
							{
								deleteKeys.Add(pair.Key);
								continue;
							}

							pair.Value.SyncDo(list =>
							{
								while (list.Count > 0)
								{
									if (list.First.Value.Time >= _firstValueTime)
										break;

									list.RemoveFirst();
								}
							});
						}

						deleteKeys.ForEach(c => _candleValues.Remove(c));
					}
				}
			}

			public void AddValue(Candle candle, ICandleBuilderSourceValue value)
			{
				if (candle == null)
					throw new ArgumentNullException("candle");

				if (value == null)
					throw new ArgumentNullException("value");

				_candleValues.SafeAdd(candle).AddLast(value);

				if (_firstValueTime == default(DateTime))
					_firstValueTime = value.Time;

				_lastValueTime = value.Time;

				RecycleValues();
			}

			public IEnumerable<ICandleBuilderSourceValue> GetValues(Candle candle)
			{
				if (candle == null)
					throw new ArgumentNullException("candle");

				var trades = _candleValues.TryGetValue(candle);
				return trades != null ? trades.SyncGet(c => c.ToArray()) : Enumerable.Empty<ICandleBuilderSourceValue>();
			}
		}

		private readonly SynchronizedDictionary<CandleSeries, SeriesInfo> _info = new SynchronizedDictionary<CandleSeries, SeriesInfo>();
		private TimeSpan _maxValuesKeepTime;
		
		/// <summary>
		/// Создать <see cref="CandleBuilderContainer"/>.
		/// </summary>
		public CandleBuilderContainer()
		{
			ValuesKeepTime = TimeSpan.Zero;
		}

		private TimeSpan _valuesKeepTime;

		/// <summary>
		/// Время хранения <see cref="ICandleBuilderSourceValue"/> в памяти. По-умолчанию равно нулю (хранение отсутствует).
		/// </summary>
		public TimeSpan ValuesKeepTime
		{
			get { return _valuesKeepTime; }
			set
			{
				if (value < TimeSpan.Zero)
					throw new ArgumentOutOfRangeException("value", value, "Время хранения данных не может быть отрицательным.");

				_valuesKeepTime = value;
				_maxValuesKeepTime = TimeSpan.FromTicks((long)(value.Ticks * 1.5));

				_info.SyncDo(d => d.ForEach(p => p.Value.RecycleValues()));
			}
		}

		/// <summary>
		/// Известить контейнер для начале получения данных для серии.
		/// </summary>
		/// <param name="series">Серия свечек.</param>
		/// <param name="from">Начальная дата, с которой будут получаться данные.</param>
		/// <param name="to">Конечная дата, до которой будут получаться данные.</param>
		public void Start(CandleSeries series, DateTime from, DateTime to)
		{
			if (series == null)
				throw new ArgumentNullException("series");

			var info = _info.SafeAdd(series, key => new SeriesInfo(this));
			info.Reset(from);
		}

		/// <summary>
		/// Добавить данные для свечки.
		/// </summary>
		/// <param name="series">Серия свечек.</param>
		/// <param name="candle">Свечка, для которой нужно добавить данные.</param>
		/// <param name="value">Новые данные.</param>
		public void AddValue(CandleSeries series, Candle candle, ICandleBuilderSourceValue value)
		{
			if (_valuesKeepTime == TimeSpan.Zero)
			    return;

			GetInfo(series).AddValue(candle, value);
		}

		/// <summary>
		/// Получить все данные по свечке.
		/// </summary>
		/// <param name="series">Серия свечек.</param>
		/// <param name="candle">Свечка, по которой нужно найти данные.</param>
		/// <returns>Найденные данные.</returns>
		public IEnumerable<ICandleBuilderSourceValue> GetValues(CandleSeries series, Candle candle)
		{
			return GetInfo(series).GetValues(candle);
		}

		private SeriesInfo GetInfo(CandleSeries series)
		{
			if (series == null)
				throw new ArgumentNullException("series");

			var info = _info.TryGetValue(series);

			if (info == null)
				throw new InvalidOperationException("Серия {0} не была ранее запущена.".Put(series));

			return info;
		}
	}
}